# -------------------------------------------------------------------------------------------------
#  Copyright (C) 2015-2024 Nautech Systems Pty Ltd. All rights reserved.
#  https://nautechsystems.io
#
#  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
#  You may not use this file except in compliance with the License.
#  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
# -------------------------------------------------------------------------------------------------

import datetime as dt
from decimal import Decimal
from typing import Any
import numpy as np
import pandas as pd
import pytz

from nautilus_trader.model.currencies import ADA
from nautilus_trader.model.currencies import AUD
from nautilus_trader.model.currencies import BTC
from nautilus_trader.model.currencies import ETH
from nautilus_trader.model.currencies import USD
from nautilus_trader.model.currencies import USDC
from nautilus_trader.model.currencies import USDT
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.identifiers import Symbol
from nautilus_trader.model.identifiers import TradeId
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.instruments import CryptoFuture
from nautilus_trader.model.instruments import CryptoPerpetual
from nautilus_trader.model.instruments import CurrencyPair
from nautilus_trader.model.objects import Money
from nautilus_trader.model.objects import Price
from nautilus_trader.model.objects import Quantity



class InstrumentProvider:
    """
    Provides instrument template methods for backtesting.
    """

    __test__ = False  # Prevents pytest from collecting this as a test class

    @staticmethod
    def adabtc_binance() -> CurrencyPair:
        """
        Return the Binance Spot ADA/BTC instrument for backtesting.

        Returns
        -------
        CurrencyPair

        """
        return CurrencyPair(
            instrument_id=InstrumentId(
                symbol=Symbol("ADABTC"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("ADABTC"),
            base_currency=ADA,
            quote_currency=BTC,
            price_precision=8,
            size_precision=8,
            price_increment=Price(1e-08, precision=8),
            size_increment=Quantity(1e-08, precision=8),
            lot_size=None,
            max_quantity=Quantity.from_int(90_000_000),
            min_quantity=Quantity.from_int(1),
            max_notional=None,
            min_notional=Money(0.00010000, BTC),
            max_price=Price(1000, precision=8),
            min_price=Price(1e-8, precision=8),
            margin_init=Decimal("0"),
            margin_maint=Decimal("0"),
            maker_fee=Decimal("0.0010"),
            taker_fee=Decimal("0.0010"),
            ts_event=0,
            ts_init=0,
        )

    @staticmethod
    def adausdt_binance() -> CurrencyPair:
        """
        Return the Binance Spot ADA/USDT instrument for backtesting.

        Returns
        -------
        CurrencyPair

        """
        return CurrencyPair(
            instrument_id=InstrumentId(
                symbol=Symbol("ADAUSDT"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("ADAUSDT"),
            base_currency=ADA,
            quote_currency=USDT,
            price_precision=4,
            size_precision=1,
            price_increment=Price(0.0001, precision=4),
            size_increment=Quantity(0.1, precision=1),
            lot_size=Quantity(0.1, precision=1),
            max_quantity=Quantity(900_000, precision=1),
            min_quantity=Quantity(0.1, precision=1),
            max_notional=None,
            min_notional=Money(0.00010000, BTC),
            max_price=Price(1000, precision=4),
            min_price=Price(1e-8, precision=4),
            margin_init=Decimal("0"),
            margin_maint=Decimal("0"),
            maker_fee=Decimal("0.0010"),
            taker_fee=Decimal("0.0010"),
            ts_event=0,
            ts_init=0,
        )

    @staticmethod
    def btcusdt_binance() -> CurrencyPair:
        """
        Return the Binance Spot BTCUSDT instrument for backtesting.

        Returns
        -------
        CurrencyPair

        """
        return CurrencyPair(
            instrument_id=InstrumentId(
                symbol=Symbol("BTCUSDT"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("BTCUSDT"),
            base_currency=BTC,
            quote_currency=USDT,
            price_precision=2,
            size_precision=6,
            price_increment=Price(1e-02, precision=2),
            size_increment=Quantity(1e-06, precision=6),
            lot_size=None,
            max_quantity=Quantity(9000, precision=6),
            min_quantity=Quantity(1e-06, precision=6),
            max_notional=None,
            min_notional=Money(10.00000000, USDT),
            max_price=Price(1000000, precision=2),
            min_price=Price(0.01, precision=2),
            margin_init=Decimal(0),
            margin_maint=Decimal(0),
            maker_fee=Decimal("0.001"),
            taker_fee=Decimal("0.001"),
            ts_event=0,
            ts_init=0,
        )

    @staticmethod
    def btcusdt_perp_binance() -> CurrencyPair:
        """
        Return the Binance Futures BTCUSDT instrument for backtesting.

        Returns
        -------
        CryptoPerpetual

        """
        return CryptoPerpetual(
            instrument_id=InstrumentId(
                symbol=Symbol("BTCUSDT-PERP"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("BTCUSDT"),
            base_currency=BTC,
            quote_currency=USDT,
            settlement_currency=USDT,
            is_inverse=False,
            price_precision=1,
            price_increment=Price.from_str("0.1"),
            size_precision=3,
            size_increment=Quantity.from_str("0.001"),
            max_quantity=Quantity.from_str("1000.000"),
            min_quantity=Quantity.from_str("0.001"),
            max_notional=None,
            min_notional=Money(10.00, USDT),
            max_price=Price.from_str("809484.0"),
            min_price=Price.from_str("261.1"),
            margin_init=Decimal("0.0500"),
            margin_maint=Decimal("0.0250"),
            maker_fee=Decimal("0.000200"),
            taker_fee=Decimal("0.000180"),
            ts_event=1646199312128000000,
            ts_init=1646199342953849862,
        )

    @staticmethod
    def ethusdt_binance() -> CurrencyPair:
        """
        Return the Binance Spot ETHUSDT instrument for backtesting.

        Returns
        -------
        CurrencyPair

        """
        return CurrencyPair(
            instrument_id=InstrumentId(
                symbol=Symbol("ETHUSDT"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("ETHUSDT"),
            base_currency=ETH,
            quote_currency=USDT,
            price_precision=2,
            size_precision=5,
            price_increment=Price(1e-02, precision=2),
            size_increment=Quantity(1e-05, precision=5),
            lot_size=None,
            max_quantity=Quantity(9000, precision=5),
            min_quantity=Quantity(1e-05, precision=5),
            max_notional=None,
            min_notional=Money(10.00, USDT),
            max_price=Price(1000000, precision=2),
            min_price=Price(0.01, precision=2),
            margin_init=Decimal("1.00"),
            margin_maint=Decimal("0.35"),
            maker_fee=Decimal("0.0001"),
            taker_fee=Decimal("0.0001"),
            ts_event=0,
            ts_init=0,
        )

    @staticmethod
    def ethusdt_perp_binance() -> CryptoPerpetual:
        """
        Return the Binance Futures ETHUSDT-PERP instrument for backtesting.

        Returns
        -------
        CryptoPerpetual

        """
        return CryptoPerpetual(
            instrument_id=InstrumentId(
                symbol=Symbol("ETHUSDT-PERP"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("ETHUSDT"),
            base_currency=ETH,
            quote_currency=USDT,
            settlement_currency=USDT,
            is_inverse=False,
            price_precision=2,
            size_precision=3,
            price_increment=Price.from_str("0.01"),
            size_increment=Quantity.from_str("0.001"),
            max_quantity=Quantity.from_str("10000.000"),
            min_quantity=Quantity.from_str("0.001"),
            max_notional=None,
            min_notional=Money(10.00, USDT),
            max_price=Price.from_str("152588.43"),
            min_price=Price.from_str("29.91"),
            margin_init=Decimal("1.00"),
            margin_maint=Decimal("0.35"),
            maker_fee=Decimal("0.0002"),
            taker_fee=Decimal("0.0004"),
            ts_event=1646199312128000000,
            ts_init=1646199342953849862,
        )

    @staticmethod
    def btcusdt_future_binance(
        activation: pd.Timestamp | None = None,
        expiration: pd.Timestamp | None = None,
    ) -> CryptoFuture:
        """
        Return the Binance Futures BTCUSDT instrument for backtesting.

        Parameters
        ----------
        activation : pd.Timestamp, optional
            The activation (UTC) for the contract.
        expiration : pd.Timestamp, optional
            The expiration (UTC) for the contract.

        Returns
        -------
        CryptoFuture

        """
        if activation is None:
            activation = pd.Timestamp("2021-12-25", tz=pytz.utc)
        if expiration is None:
            expiration = pd.Timestamp("2022-3-25", tz=pytz.utc)
        return CryptoFuture(
            instrument_id=InstrumentId(
                symbol=Symbol(f"BTCUSDT_{expiration.strftime('%y%m%d')}"),
                venue=Venue("BINANCE"),
            ),
            raw_symbol=Symbol("BTCUSDT"),
            underlying=BTC,
            quote_currency=USDT,
            settlement_currency=USDT,
            is_inverse=False,
            activation_ns=activation.value,
            expiration_ns=expiration.value,
            price_precision=2,
            size_precision=6,
            price_increment=Price(1e-02, precision=2),
            size_increment=Quantity(1e-06, precision=6),
            max_quantity=Quantity(9000, precision=6),
            min_quantity=Quantity(1e-06, precision=6),
            max_notional=None,
            min_notional=Money(10.00000000, USDT),
            max_price=Price(1000000, precision=2),
            min_price=Price(0.01, precision=2),
            margin_init=Decimal(0),
            margin_maint=Decimal(0),
            maker_fee=Decimal("0.001"),
            taker_fee=Decimal("0.001"),
            ts_event=0,
            ts_init=0,
        )
